package xiaojian.toolkit.jmx.model;

import java.beans.PropertyEditor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import xiaojian.toolkit.util.ClassUtils;
import xiaojian.toolkit.util.PropertyEditorSource;
import xiaojian.toolkit.util.VariantArray;

public class OperationsContext {
	private static final Logger LOG = LoggerFactory
			.getLogger(OperationsContext.class);
	private final String _name;
	private Unit[] _units;

	public static class Unit {
		private final ObjectName _objectName;
		private final OpParams[] _operations;

		public class OpParams {
			private final MBeanOperationInfo _info;
			private final MBeanParameterInfo[] _signature;
			private final List<Object> _parameters;
			private final int _parameterCount;

			private OpParams(MBeanOperationInfo info) {
				this._info = info;
				this._signature = this._info.getSignature();
				this._parameterCount = this._signature.length;
				this._parameters = new ArrayList(this._parameterCount);
				for (int idx = 0; idx < this._parameterCount; idx++) {
					this._parameters.add(null);
				}
			}

			public MBeanOperationInfo getOperationInfo() {
				return this._info;
			}

			public void setParameterValue(int idx, Object value) {
				if (!isParameterIndexValid(idx)) {
					OperationsContext.LOG.error(
							"try to set value {} to invalid parameter idx {}",
							value, Integer.valueOf(idx));
					throw new RuntimeException("invalid parameter idx");
				}
				this._parameters.set(idx, value);
				if (OperationsContext.LOG.isDebugEnabled()) {
					OperationsContext.LOG.debug("set param {} with value {}",
							parameterOf(idx), value);
				}
			}

			public void setTextAsParameterValue(PropertyEditorSource source,
					int idx, String text) {
				if (!isParameterIndexValid(idx)) {
					OperationsContext.LOG.error(
							"try to set value {} to invalid parameter idx {}",
							text, Integer.valueOf(idx));
					throw new RuntimeException("invalid parameter idx");
				}
				MBeanParameterInfo paramInfo = parameterOf(idx);
				PropertyEditor editor = source.getPropertyEditor(paramInfo
						.getType());
				if (null == editor) {
					OperationsContext.LOG
							.info("set param {} with value {} failed, can't found propertyEditor",
									paramInfo, text);
					return;
				}
				editor.setAsText(text);
				setParameterValue(idx, editor.getValue());
			}

			public Object[] genParams() {
				return this._parameters.toArray();
			}

			public String[] genSignatures() {
				String[] signatures = new String[this._signature.length];
				for (int idx = 0; idx < signatures.length; idx++) {
					signatures[idx] = parameterOf(idx).getType();
				}
				return signatures;
			}

			public VariantArray toVariantArray() {
				return new VariantArray() {
					public int getArrayCount() {
						return OperationsContext.Unit.OpParams.this._parameterCount;
					}

					public Class<?> getTypeAt(int idx) {
						return ClassUtils
								.type2Class(OperationsContext.Unit.OpParams.this._signature[idx]
										.getType());
					}

					public Object getVariantAt(int idx) {
						return OperationsContext.Unit.OpParams.this._parameters
								.get(idx);
					}

					public void setVariantAt(int idx, Object variant) {
						OperationsContext.Unit.OpParams.this._parameters.set(
								idx, variant);
					}

					public String getNameAt(int idx) {
						return OperationsContext.Unit.OpParams.this._signature[idx]
								.getName();
					}
				};
			}

			public String toString() {
				return "OpParams [_info=" + this._info + ", _signature="
						+ Arrays.toString(this._signature) + ", _parameters="
						+ this._parameters + ", _parameterCount="
						+ this._parameterCount + "]";
			}

			public boolean hasDefaultParams() {
				return this._hasDefaultParams;
			}

			public void resetDefaultParams(PropertyEditorSource source) {
				for (int idx = 0; idx < this._parameterCount; idx++) {
					MBeanParameterInfo info = this._signature[idx];
					setTextAsParameterValue(source, idx,
							source.getDefaultValueAsText(ClassUtils
									.type2Class(info.getType())));
				}
				this._hasDefaultParams = true;
			}

			public Object invoke(MBeanServerConnection mbsc) throws Exception {
				return mbsc.invoke(OperationsContext.Unit.this._objectName,
						getOperationInfo().getName(), genParams(),
						genSignatures());
			}

			private boolean isParameterIndexValid(int idx) {
				return (idx >= 0) && (idx < this._parameterCount);
			}

			private MBeanParameterInfo parameterOf(int idx) {
				return this._signature[idx];
			}

			private boolean _hasDefaultParams = false;
		}

		public ObjectName getObjectName() {
			return this._objectName;
		}

		public OpParams[] getOperations() {
			return this._operations;
		}

		public Unit(ObjectName on, MBeanOperationInfo... infos) {
			this._objectName = on;
			this._operations = new OpParams[infos.length];
			for (int idx = 0; idx < infos.length; idx++) {
				this._operations[idx] = new OpParams(infos[idx]);
			}
		}

		public String toString() {
			return "[on=" + this._objectName + ", operations="
					+ Arrays.toString(this._operations) + "]";
		}
	}

	public String getName() {
		return this._name;
	}

	public Unit[] getOpUnits() {
		return this._units;
	}

	public OperationsContext(String name, Unit... units) {
		this._name = name;
		this._units = units;
	}

	public String toString() {
		return "opsCtx [name=" + this._name + ", units="
				+ Arrays.toString(this._units) + "]";
	}
}
